import crypto from "crypto"
import { h, Schema } from "koishi"

export const name = "github-webhook"
export const inject = {
  required: [
    "server",
    "database", // assignee
  ],
  optional: ["assets"],
}

export const Config = Schema.object({
  secret: String,
  path: Schema.string().default("/github/webhook-lite"),
  channels: Schema.array(String),
  owners: Schema.array(String),
})

export function apply(ctx, config) {
  function i18n(path, data) {
    return h("i18n", { path: "lnnbot.github-webhook." + path }, data)
  }

  function i18no(path, data) {
    return h("i18n:object", { path: "lnnbot.github-webhook." + path, data })
  }

  function opengraph(url, hashSource = new Date().toJSON()) {
    const hash = crypto.hash("sha256", hashSource, "hex").slice(0, 8)
    const path = new URL(url).pathname
    return h.image(`https://opengraph.githubassets.com/${hash}${path}`)
  }

  async function send(content) {
    ctx.logger.debug("send %o", content)
    if (ctx.assets) try {
      content = await ctx.assets.transform(h.normalize(content))
    } catch (e) {
      ctx.logger.warn(e)
    }
    await ctx.broadcast(config.channels.slice(0), content)
  }

  ctx.server.post(config.path, async res => {
    const payload = res.request.body
    const event = res.headers["x-github-event"]
    ctx.logger.debug("%o %o", event, payload)
    const signature = res.headers["x-hub-signature-256"]
    const hmac = crypto.createHmac("sha256", config.secret)
    const digest = "sha256=" + hmac.update(res.request.body[Symbol.for("unparsedBody")]).digest("hex")
    if (signature !== digest) {
      res.status = 403
      res.body = "Forbidden"
      return
    }
    handleEvent(event, payload)
    res.status = 200
    res.body = "Webhook received"
  })

  function handleEvent(event, payload) {
    const repo = payload.repository
    if (!repo) {
      ctx.logger.debug("Ignore %o event because it is not associated with a repository", event)
      return
    }
    if (!config.owners.includes(repo.owner.login)) {
      ctx.logger.info("Ignore %o event in repository %o because its owner %o is not in configured whitelist", event, repo.full_name, repo.owner.login)
      return
    }
    const sender = payload.sender
    const stars = repo.stargazers_count
    const repoUrl = repo.html_url
    if (event === "star") {
      if (payload.action === "created") {
        send([
          i18no("star.created", { user: sender.login, repo: repo.full_name, stars }),
          h("message", opengraph(repoUrl)),
        ])
      } else if (payload.action === "deleted") {
        send([
          i18no("star.deleted", { user: sender.login, repo: repo.full_name, stars }),
          h("message", opengraph(repoUrl)),
        ])
      }
    } else if (event === "push") {
      const pusher = payload.pusher
      const commits = payload.commits
      const branch = payload.ref?.replace("refs/heads/", "")

      // Skip pages deployment commits from old workflows
      if (pusher.name === "github-actions[bot]" && branch === "gh-pages") return

      if (commits.length) {
        const images = commits.map(c => h("message", opengraph(c.url, c.id)))
        send([
          i18no("push", {
            user: pusher.name,
            repo: repo.full_name,
            branch,
            commitCount: commits.length,
          }),
          h("br"),
          i18n("details", [payload.compare]),
          images.length > 1 ? h("message", { forward: true }, images) : images[0],
        ])
      }
    } else if (event === "workflow_run" && payload.action === "completed") {
      const workDetial = payload.workflow_run
      if (workDetial.event !== "dynamic") {
        send([
          i18no("workflow_run.completed", {
            user: sender.login,
            repo: repo.full_name,
            event: workDetial.event,
            name: workDetial.name,
            conclusion: workDetial.conclusion,
            display_title: workDetial.display_title,
          }),
          h("br"),
          i18n("details", [workDetial.html_url]),
        ])
      }
    } else if (event === "issues") {
      if (payload.action === "opened") {
        send([
          i18no("issue.opened", { user: sender.login, repo: repo.full_name }),
          h("br"),
          i18n("details", [payload.issue.html_url]),
          h("message", opengraph(payload.issue.html_url)),
        ])
      } else if (payload.action === "closed") {
        send([
          i18no("issue.closed", { user: sender.login, repo: repo.full_name }),
          h("br"),
          i18n("details", [payload.issue.html_url]),
          h("message", opengraph(payload.issue.html_url)),
        ])
      }
    } else if (event === "pull_request") {
      if (payload.action === "opened") {
        send([
          i18no("pull_request.opened", { user: sender.login, repo: repo.full_name }),
          h("br"),
          i18n("details", [payload.pull_request.html_url]),
          h("message", opengraph(payload.pull_request.html_url)),
        ])
      } else if (payload.action === "closed") {
        if (payload.pull_request.merged) {
          send([
            i18no("pull_request.merged", { user: sender.login, repo: repo.full_name }),
            h("br"),
            i18n("details", [payload.pull_request.html_url]),
            h("message", opengraph(payload.pull_request.html_url)),
          ])
        } else {
          send([
            i18no("pull_request.closed", { user: sender.login, repo: repo.full_name }),
            h("br"),
            i18n("details", [payload.pull_request.html_url]),
            h("message", opengraph(payload.pull_request.html_url)),
          ])
        }
      }
    } else if (event === "issue_comment") {
      if (payload.action === "created") {
        send([
          i18no("issue_comment.created", { user: sender.login, repo: repo.full_name }),
          h("br"),
          i18n("details", [payload.comment.html_url]),
          h("message", opengraph(payload.comment.html_url)),
        ])
      }
    } else if (event === "release") {
      if (payload.action === "released") {
        send([
          i18no("release.released", { user: sender.login, repo: repo.full_name, tag: payload.release.tag_name }),
          h("br"),
          i18n("details", [payload.release.html_url]),
          h("message", opengraph(payload.release.html_url)),
        ])
      }
    }
  }

  ctx.i18n.define("zh-CN", "lnnbot.github-webhook", {
    details: "详细内容：{0}",
    star: {
      created: "{user} 收藏了仓库 {repo}，仓库累计收藏数：{stars}。",
      deleted: "{user} 取消收藏仓库 {repo}，仓库剩余收藏数：{stars}。",
    },
    push: "{user} 向仓库 {repo} 的 {branch} 分支推送了 {commitCount} 次更新。",
    workflow_run: {
      completed: "{user} 触发了仓库 {repo} 的自动化任务。<br/>发起事件：{event}<br/>任务名称：{name}<br/>执行结果：{conclusion}<br/>相关提交：{display_title}",
    },
    issue: {
      opened: "{user} 在仓库 {repo} 中创建了问题。",
      closed: "{user} 关闭了仓库 {repo} 中的问题。",
      reopened: "{user} 重新开启了仓库 {repo} 中的问题。",
    },
    pull_request: {
      opened: "{user} 向仓库 {repo} 提交了合并请求。",
      closed: "{user} 关闭了仓库 {repo} 的合并请求。",
      merged: "{user} 合并了仓库 {repo} 的合并请求。",
    },
    issue_comment: {
      created: "{user} 在仓库 {repo} 中发表了评论。",
    },
    release: {
      released: "{user} 发布了仓库 {repo} 的新版本：{tag}",
    },
  })
}
